﻿/*
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

using System;
using System.Windows.Forms;
using Microsoft.DirectX;
using Microsoft.DirectX.DirectInput;

namespace Daidalos.Input
{
    /// <summary>
    /// 
    /// </summary>
    public sealed class Peripherals : SingletonManager<Peripherals>
    {

        #region Attributes
        /// <summary>
        ///    A reference to the client associated to the engine.
        /// </summary>
        private Form parentForm = null;

        /// <summary>
        ///    Indicates if the mouse interactions are currently
        ///    suspended.
        /// </summary>
        private bool peripheralsSuspended = false;

        /// <summary>
        ///    The mouse cursor's x-position relative to the top-left corner
        ///    of the client area.
        /// </summary>
        private int mouseClientRelativeX = -1;

        /// <summary>
        ///    The mouse cursor's y-position relative to the top-left corner
        ///    of the client area.
        /// </summary>
        private int mouseClientRelativeY = -1;

        /// <summary>
        ///    Has the manager been initialized?
        /// </summary>
        private bool initialized = false;

        /// <summary>
        ///    The DirectInput keyboard device.
        /// </summary>
        private Device keyboardDevice = null;

        /// <summary>
        ///    The DirectInput mouse device.
        /// </summary>
        private Device mouseDevice = null;

        /// <summary>
        ///    The state stores the state of the keyboard's keys.
        /// </summary>
        private KeyboardState keyboardState = null;

        /// <summary>
        ///    The state stores the state of the the mouse.
        /// </summary>
        private MouseState mouseState;
        #endregion

        #region Properties
        /// <summary>
        ///    Implements full access to the mouse's x-position relative
        ///    to the client area.
        /// </summary>
        public int MouseClientRelativeX
        {
            get
            {
                return mouseClientRelativeX;
            }
        }

        /// <summary>
        ///    Implements full access to the mouse's y-position relative
        ///    to the client area.
        /// </summary>
        public int MouseClientRelativeY
        {
            get
            {
                return mouseClientRelativeY;
            }
        }

        /// <summary>
        ///    States, if the manager has been initialized.
        /// </summary>
        public bool Initialized
        {
            get
            {
                return initialized;
            }
        }
        #endregion

        #region Constructors
        /// <summary>
        ///    Initializes the instance.
        /// </summary>
        public Peripherals()
        {
            // Try to create the keyboard device.
            keyboardDevice = new Device(SystemGuid.Keyboard);
            if (keyboardDevice == null)
            {
                throw new DirectXException("Failed to create a keyboard device!");
            }

            // Try to create the mouse device.
            mouseDevice = new Device(SystemGuid.Mouse);
            if (keyboardDevice == null)
            {
                throw new DirectXException("Failed to create a mouse device!");
            }
        }

        /// <summary>
        ///    The destructor disposes of the associated devices.
        /// </summary>
        ~Peripherals()
        {
            // Dipose of the keyboard if necessary.
            if (keyboardDevice != null)
            {
                keyboardDevice.Dispose();
            }
        }
        #endregion

        #region Methods
        /// <summary>
        ///    This method initializes all peripherals managed by the input
        ///    manager.
        /// </summary>
        public void InitializePeripherals(Form parentForm)
        {
            // Mark the instance as "not initialized".
            initialized = false;

            // Store the parent form.
            this.parentForm = parentForm;
            
            // Verify arguments.
            if (parentForm == null)
            {
                return;
            }

            // Register the mouse interaction & suspension controls.
            parentForm.Activated += new EventHandler(this.OnParentActivated);
            parentForm.Deactivate += new EventHandler(this.OnParentDeactivated);
            parentForm.MouseMove += new MouseEventHandler(this.OnParentMouseMoved);

            // Set the cooperative level of the keyboard.
            keyboardDevice.SetCooperativeLevel(parentForm, CooperativeLevelFlags.Foreground | CooperativeLevelFlags.NonExclusive);

            // Acquire the keyboard.
            keyboardDevice.Acquire();

            // Poll the keyboard to establish an initial state.
            PollKeyboard();

            // Set the cooperative level of the mouse.
            mouseDevice.SetCooperativeLevel(parentForm, CooperativeLevelFlags.Foreground | CooperativeLevelFlags.NonExclusive);

            // Set the data format for the mouse.
            mouseDevice.SetDataFormat(DeviceDataFormat.Mouse);

            // Set the mouse mode to "relative addressing". Note that relative addressing is used to move the player's view for
            // instance. Absolute addressing (used by the UI for instance) is done by the client form and not via DirectInput!
            mouseDevice.Properties.AxisModeAbsolute = false;

            // Acquire the mouse.
            mouseDevice.Acquire();

            // Poll the mouse to establish an initial state.
            PollMouse();
                   
            // Poll the peripherals to get a grip on their initial states.
            PollInputPeripherals();

            // Mark the input peripherals as initialized.
            initialized = true;
        }

        /// <summary>
        ///    This method is called, when the parent window regains
        ///    focus.
        /// </summary>
        /// <param name="dispatcher">
        ///    The object dispatching the event.
        /// </param>
        /// <param name="arguments">
        ///    The arguments of the event.
        /// </param>
        public void OnParentActivated(object dispatcher, EventArgs arguments)
        {
            // Resume interactions with the input devices.
            ResumePeripheralInteractions();
        }

        /// <summary>
        ///    This method is called, when the parent window loses
        ///    focus.
        /// </summary>
        /// <param name="dispatcher">
        ///    The object dispatching the event.
        /// </param>
        /// <param name="arguments">
        ///    The arguments of the event.
        /// </param>
        public void OnParentDeactivated(object dispatcher, EventArgs arguments)
        {
            // Halt interactions with the input devices.
            HaltPeripheralInteractions();
        }
        
        /// <summary>
        ///    This method is called everytime the mouse is moved on the
        ///    client area. It is used to keep track on the mouse cursor 
        ///    position relative to the client area.
        /// </summary>
        public void OnParentMouseMoved(object dispatcher, MouseEventArgs arguments)
        {
            // Store the new positions.
            mouseClientRelativeX = arguments.X;
            mouseClientRelativeY = arguments.Y;
        }

        /// <summary>
        ///    Reacquires the input devices after the client focus has been
        ///    regained.
        /// </summary>
        public void ResumePeripheralInteractions()
        {
            // Acquire the mouse.
            try
            {
                mouseDevice.Acquire();
            }
            catch (Exception) { };

            // Acquire the keyboard.
            try
            {
                keyboardDevice.Acquire();
            }
            catch (Exception) { };

            // Unset suspension flag.
            peripheralsSuspended = false;
        }

        /// <summary>
        ///    Calling this method puts all mouse interactions at halt.
        /// </summary>
        /// <remarks>
        ///    Should be used to react on client focus loss.
        /// </remarks>        
        public void HaltPeripheralInteractions()
        {
            // Unacquire the mouse.
            mouseDevice.Unacquire();

            // Unacquire the keyboard.
            keyboardDevice.Unacquire();

            // Set suspension flag.
            peripheralsSuspended = true;
        }
        #endregion

        #region DevicePolling
        /// <summary>
        ///    Polls the default keyboard device for its current state.
        /// </summary>
        private void PollKeyboard()
        {
            // Retrieve the keystate.
            keyboardState = keyboardDevice.GetCurrentKeyboardState();
        }

        /// <summary>
        ///    Polls the mouse's state.
        /// </summary>
        private void PollMouse()
        {
            // Retrieve the mousestate.
            mouseState = mouseDevice.CurrentMouseState;
        }

        /// <summary>
        ///    This method polls all active input peripherals.
        /// </summary>
        public void PollInputPeripherals()
        {
            // Are the devices suspended?
            if (peripheralsSuspended)
            {
                return;
            }

            // Poll the keyboard.
            PollKeyboard();

            // Poll the mouse.
            PollMouse();
        }
        #endregion

        #region MouseStateAccessors
        /// <summary>
        ///    Implements access to the x-position of the mouse pointer.
        /// </summary>
        public int MouseDeltaX
        {
            get
            {
                return peripheralsSuspended ? 0 : mouseState.X;
            }
        }

        /// <summary>
        ///    Implements access to the y-position of the mouse pointer.
        /// </summary>
        public int MouseDeltaY
        {
            get
            {
                return peripheralsSuspended ? 0 : mouseState.Y;
            }
        }

        /// <summary>
        ///    Returns an array that contains the state of the mouse buttons.
        /// </summary>
        public bool[] MouseButtons
        {
            get
            {
                byte[] buttonStates = mouseState.GetMouseButtons();
                bool[] booleanStates = new bool[buttonStates.Length];
                for (int k = 0; k < buttonStates.Length; ++k)
                {
                    booleanStates[k] = buttonStates[k] != 0;
                }
                return booleanStates;
            }
        }
        #endregion

        #region KeyStateAccessors
        /// <summary>
        ///    Is the escape key currently pressed?
        /// </summary>
        public bool EscapePressed
        {
            get
            {
                return peripheralsSuspended ? false : keyboardState[Key.Escape];
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public bool ConsoleKeyPressed
        {
            get
            {
                return peripheralsSuspended ? false : keyboardState[Key.Return];
            }
        }

        public char GetKey()
        {
            char res = (char)0;
            if (keyboardState[Key.A])
                res = 'a';
            else if (keyboardState[Key.B])
                res = 'b';
            else if (keyboardState[Key.C])
                res = 'c';
            else if (keyboardState[Key.D])
                res = 'd';
            else if (keyboardState[Key.E])
                res = 'e';
            else if (keyboardState[Key.F])
                res = 'f';
            else if (keyboardState[Key.G])
                res = 'g';
            else if (keyboardState[Key.H])
                res = 'h';
            else if (keyboardState[Key.I])
                res = 'i';
            else if (keyboardState[Key.J])
                res = 'j';
            else if (keyboardState[Key.K])
                res = 'k';
            else if (keyboardState[Key.L])
                res = 'l';
            else if (keyboardState[Key.M])
                res = 'm';
            else if (keyboardState[Key.N])
                res = 'n';
            else if (keyboardState[Key.O])
                res = 'o';
            else if (keyboardState[Key.P])
                res = 'p';
            else if (keyboardState[Key.Q])
                res = 'q';
            else if (keyboardState[Key.R])
                res = 'r';
            else if (keyboardState[Key.S])
                res = 's';
            else if (keyboardState[Key.T])
                res = 't';
            else if (keyboardState[Key.U])
                res = 'u';
            else if (keyboardState[Key.V])
                res = 'v';
            else if (keyboardState[Key.W])
                res = 'w';
            else if (keyboardState[Key.X])
                res = 'x';
            else if (keyboardState[Key.Y])
                res = 'y';
            else if (keyboardState[Key.Z])
                res = 'z';
            else if (keyboardState[Key.Space])
                res = ' ';
            else if (keyboardState[Key.Return])
                res = (char)1;
            else if (keyboardState[Key.Comma])
                res = ',';
            else if (keyboardState[Key.BackSpace])
                res = (char)2;
            else if (keyboardState[Key.Minus] && (keyboardState[Key.LeftShift] || keyboardState[Key.RightShift]))
                res = '_';

            if(keyboardState[Key.LeftShift] || keyboardState[Key.RightShift])
                res = char.ToUpper(res);

            return res;
        }
        #endregion

    }

}
